Passed
Branch riff-file (882407)
by Rafael S.
03:27
created

RIFFFile.readString_   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The RIFFFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import {unpackString, unpack} from 'byte-data';
31
32
/**
33
 * A class to perform low-level reading of RIFF files.
34
 */
35
export default class RIFFFile {
36
37
  constructor() {
38
    
39
    /** @type {number} */
40
    this.head_ = 0;
41
    /** @type {!Object} */
42
    this.uInt32_ = {bits: 32, be: false};
43
    /** @type {!Object} */
44
    this.uInt16_ = {bits: 16, be: false};
45
    /**
46
     * The container identifier.
47
     * 'RIFF', 'RIFX' and 'RF64' are supported.
48
     * @type {string}
49
     */
50
    this.container = '';
51
    /**
52
     * @type {number}
53
     */
54
    this.chunkSize = 0;
55
    /**
56
     * The format.
57
     * Always 'WAVE'.
58
     * @type {string}
59
     */
60
    this.format = '';
61
    /**
62
     * A object defining the start and end of all chunks in a wav buffer.
63
     * @type {!Object}
64
     */
65
    this.signature = {};
66
  }
67
68
  /**
69
   * Return the chunks in a RIFF/RIFX file.
70
   * @param {!Uint8Array} buffer The file bytes.
71
   */
72
  getSignature_(buffer) {
73
      this.head_ = 0;
74
      /** @type {string} */
75
      let chunkId = this.getChunkId_(buffer, 0);
76
      this.uInt32_.be = chunkId == 'RIFX';
77
      /** @type {string} */
78
      let format = unpackString(buffer, 8, 12);
79
      this.head_ += 4;
80
      this.signature = {
81
          chunkId: chunkId,
82
          chunkSize: this.getChunkSize_(buffer, 0),
83
          format: format,
84
          subChunks: this.getSubChunksIndex_(buffer)
85
      };
86
  }
87
88
  /**
89
    * Find a chunk by its fourCC_ in a array of RIFF chunks.
90
    * @param {string} chunkId The chunk fourCC_.
91
    * @param {boolean} multiple True if there may be multiple chunks
92
    *    with the same chunkId.
93
    * @return {Object}
94
    */
95
  findChunk_(chunkId, multiple=false) {
96
    /** @type {!Array<!Object>} */
97
    let chunks = this.signature.subChunks;
98
    /** @type {!Array<!Object>} */
99
    let chunk = [];
100
    for (let i=0; i<chunks.length; i++) {
101
      if (chunks[i].chunkId == chunkId) {
102
        if (multiple) {
103
          chunk.push(chunks[i]);
104
        } else {
105
          return chunks[i];
106
        }
107
      }
108
    }
109
    if (chunkId == 'LIST') {
110
      return chunk.length ? chunk : null;
111
    }
112
    return null;
113
  }
114
115
  /**
116
   * Return the sub chunks of a RIFF file.
117
   * @param {!Uint8Array} buffer the RIFF file bytes.
118
   * @return {!Array<Object>} The subchunks of a RIFF/RIFX or LIST chunk.
119
   * @private
120
   */
121
  getSubChunksIndex_(buffer) {
122
      /** @type {!Array<!Object>} */
123
      let chunks = [];
124
      /** @type {number} */
125
      let i = this.head_;
126
      while(i <= buffer.length - 8) {
127
          chunks.push(this.getSubChunkIndex_(buffer, i));
128
          i += 8 + chunks[chunks.length - 1].chunkSize;
129
          i = i % 2 ? i + 1 : i;
130
      }
131
      return chunks;
132
  }
133
134
  /**
135
   * Return a sub chunk from a RIFF file.
136
   * @param {!Uint8Array} buffer the RIFF file bytes.
137
   * @param {number} index The start index of the chunk.
138
   * @return {!Object} A subchunk of a RIFF/RIFX or LIST chunk.
139
   * @private
140
   */
141
  getSubChunkIndex_(buffer, index) {
142
      /** @type {!Object} */
143
      let chunk = {
144
          chunkId: this.getChunkId_(buffer, index),
145
          chunkSize: this.getChunkSize_(buffer, index),
146
      };
147
      if (chunk.chunkId == 'LIST') {
148
          chunk.format = unpackString(buffer, index + 8, index + 12);
149
          this.head_ += 4;
150
          chunk.subChunks = this.getSubChunksIndex_(buffer);
151
      } else {
152
          /** @type {number} */
153
          let realChunkSize = chunk.chunkSize % 2 ?
154
              chunk.chunkSize + 1 : chunk.chunkSize;
155
          this.head_ = index + 8 + realChunkSize;
156
          chunk.chunkData = {
157
              start: index + 8,
158
              end: this.head_
159
          };
160
      }
161
      return chunk;
162
  }
163
164
  /**
165
   * Return the fourCC_ of a chunk.
166
   * @param {!Uint8Array} buffer the RIFF file bytes.
167
   * @param {number} index The start index of the chunk.
168
   * @return {string} The id of the chunk.
169
   * @private
170
   */
171
  getChunkId_(buffer, index) {
172
      this.head_ += 4;
173
      return unpackString(buffer, index, index + 4);
174
  }
175
176
  /**
177
   * Return the size of a chunk.
178
   * @param {!Uint8Array} buffer the RIFF file bytes.
179
   * @param {number} index The start index of the chunk.
180
   * @return {number} The size of the chunk without the id and size fields.
181
   * @private
182
   */
183
  getChunkSize_(buffer, index) {
184
      this.head_ += 4;
185
      return unpack(buffer, this.uInt32_, index + 4);
186
  }
187
188
  /**
189
   * Read the RIFF chunk a wave file.
190
   * @param {!Uint8Array} bytes A wav buffer.
191
   * @throws {Error} If no 'RIFF' chunk is found.
192
   * @private
193
   */
194
  readRIFFChunk_(bytes) {
195
    this.head_ = 0;
196
    this.container = this.readString_(bytes, 4);
197
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
198
      throw Error('Not a supported format.');
199
    }
200
    this.uInt16_.be = this.container === 'RIFX';
201
    this.uInt32_.be = this.uInt16_.be;
202
    this.chunkSize = this.read_(bytes, this.uInt32_);
203
  }
204
205
  /**
206
   * Read bytes as a ZSTR string.
207
   * @param {!Uint8Array} bytes The bytes.
208
   * @param {number} index the index to start reading.
209
   * @return {string} The string.
210
   * @private
211
   */
212
  readZSTR_(bytes, index=0) {
213
    for (let i = index; i < bytes.length; i++) {
214
      this.head_++;
215
      if (bytes[i] === 0) {
216
        break;
217
      }
218
    }
219
    return unpackString(bytes, index, this.head_ - 1);
220
  }
221
222
  /**
223
   * Read bytes as a string from a RIFF chunk.
224
   * @param {!Uint8Array} bytes The bytes.
225
   * @param {number} maxSize the max size of the string.
226
   * @return {string} The string.
227
   * @private
228
   */
229
  readString_(bytes, maxSize) {
230
    /** @type {string} */
231
    let str = '';
232
    str = unpackString(bytes, this.head_, this.head_ + maxSize);
233
    this.head_ += maxSize;
234
    return str;
235
  }
236
237
  /**
238
   * Read a number from a chunk.
239
   * @param {!Uint8Array} bytes The chunk bytes.
240
   * @param {!Object} bdType The type definition.
241
   * @return {number} The number.
242
   * @private
243
   */
244
  read_(bytes, bdType) {
245
    /** @type {number} */
246
    let size = bdType.bits / 8;
247
    /** @type {number} */
248
    let value = unpack(bytes, bdType, this.head_);
249
    this.head_ += size;
250
    return value;
251
  }
252
}
253